package com.turnengine.client.common.command.client;

import static com.javabi.common.dependency.DependencyFactory.getDependency;
import static java.util.concurrent.TimeUnit.SECONDS;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.javabi.command.ICommandRequestList;
import com.javabi.command.ICommandResponseList;
import com.javabi.command.IExecutableCommand;
import com.javabi.command.IExecutableCommandResponse;
import com.javabi.command.executor.ICommandExecutorService;
import com.javabi.command.response.CommandRequestList;
import com.javabi.common.date.timer.NanoTimer;
import com.turnengine.client.common.exception.TurnEngineException;

public class CommandStreamClient implements ICommandExecutorService {

	private static final Logger log = LoggerFactory.getLogger(CommandStreamClient.class);

	private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 10;
	private static final int DEFAULT_BLOCKING_TIMEOUT_SECONDS = 60;

	private final ICommandStream stream;
	private final InetSocketAddress address;

	private final AtomicInteger connectTimeout = new AtomicInteger(DEFAULT_CONNECT_TIMEOUT_SECONDS);
	private final AtomicInteger blockingTimeout = new AtomicInteger(DEFAULT_BLOCKING_TIMEOUT_SECONDS);

	public CommandStreamClient(InetSocketAddress address) {
		if (address == null) {
			throw new NullPointerException("address");
		}
		this.address = address;
		this.stream = getDependency(ICommandStream.class);
	}

	public CommandStreamClient(InetAddress address, int port) {
		this(new InetSocketAddress(address, port));
	}

	public CommandStreamClient(String address, int port) {
		this(new InetSocketAddress(address, port));
	}

	@Override
	public String toString() {
		return "CommandStreamClient[" + address.getHostName() + ":" + address.getPort() + "]";
	}

	public void setConnectTimeoutInSeconds(int seconds) {
		if (seconds < 1) {
			throw new IllegalArgumentException("seconds=" + seconds);
		}
		this.connectTimeout.set(seconds);
	}

	public void setBlockingTimeoutInSeconds(int seconds) {
		if (seconds < 1) {
			throw new IllegalArgumentException("seconds=" + seconds);
		}
		this.blockingTimeout.set(seconds);
	}

	private int getConnectTimeoutInMillis() {
		return (int) SECONDS.toMillis(connectTimeout.get());
	}

	private int getBlockingTimeoutInMillis() {
		return (int) SECONDS.toMillis(blockingTimeout.get());
	}

	@Override
	public <R> IExecutableCommandResponse<R> execute(IExecutableCommand<R> command) {
		ICommandRequestList requestList = new CommandRequestList(command);
		ICommandResponseList responseList = execute(requestList);
		return responseList.get(0);
	}

	@Override
	public ICommandResponseList execute(ICommandRequestList requestList) {
		if (requestList == null) {
			throw new NullPointerException("requestList");
		}
		try {
			NanoTimer timer = new NanoTimer();
			timer.start();

			Socket socket = new Socket();
			socket.connect(address, getConnectTimeoutInMillis());
			socket.setSoTimeout(getBlockingTimeoutInMillis());

			// Write request
			if (log.isDebugEnabled()) {
				String xml = stream.toString(requestList);
				log.debug("Request:\n" + xml);
			}
			OutputStream output = socket.getOutputStream();
			output = new BufferedOutputStream(output);
			stream.writeRequestList(requestList, output);
			output.flush();

			// Read response
			InputStream input = socket.getInputStream();
			input = new BufferedInputStream(input);
			ICommandResponseList responseList = stream.readResponseList(input);
			timer.stop();
			if (log.isDebugEnabled()) {
				String text = stream.toString(responseList);
				log.debug("Response (" + timer + "):\n" + text);
			}
			responseList.setRequestList(requestList);
			return responseList;

		} catch (IOException ioe) {
			log.error("Command Failed: " + requestList + ", server=" + address, ioe);
			throw new TurnEngineException(ioe);
		}
	}

}
